// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#ifndef airsim_core_ClockBase_hpp
#define airsim_core_ClockBase_hpp

#include <thread>
#include <chrono>
#include "Common.hpp"

namespace msr { namespace airlib {

class ClockBase {
public:
    //returns value indicating nanoseconds elapsed since some reference timepoint in history
    //typically nanoseconds from Unix epoch
    virtual TTimePoint nowNanos() const = 0;
    virtual TTimePoint getStart() const = 0;


    ClockBase()
    {
        wall_clock_start_ = Utils::getTimeSinceEpochNanos();
    }

    TTimeDelta elapsedSince(TTimePoint since) const
    {
        return elapsedBetween(nowNanos(), since);
    }
    static TTimeDelta elapsedBetween(TTimePoint second, TTimePoint first)
    {
        return (second - first) / 1.0E9;
    }
    TTimePoint addTo(TTimePoint t, TTimeDelta dt)
    {
        return static_cast<TTimePoint>(t + dt * 1.0E9);
    }
    TTimeDelta updateSince(TTimePoint& since) const
    {
        TTimePoint cur = nowNanos();
        TTimeDelta elapsed = elapsedBetween(cur, since);
        since = cur;
        return elapsed;
    }

    virtual TTimePoint step()
    {
        //by default step doesn't do anything
        //for steppeble clock, this would advance to next tick
        //for wall clocks, step() is no-op
        ++step_count_;

        return nowNanos();
    }

    uint64_t getStepCount() const
    {
        return step_count_;
    }

    virtual void sleep_for(TTimeDelta dt)
    {
        if (dt <= 0)
            return;

        static constexpr std::chrono::duration<double> MinSleepDuration(0);
        TTimePoint start = nowNanos();
        //spin wait
        while (elapsedSince(start) < dt)
            std::this_thread::sleep_for(MinSleepDuration);
    }

    double getTrueScaleWrtWallClock()
    {
        TTimePoint wall_clock_now = Utils::getTimeSinceEpochNanos();
        TTimeDelta wall_clock_elapsed = elapsedBetween(wall_clock_now, wall_clock_start_);

        TTimePoint clock_now = nowNanos();
        TTimeDelta clock_elapsed = elapsedBetween(clock_now, getStart());

        return static_cast<double>(clock_elapsed) / wall_clock_elapsed;
    }

private:
    template <typename T>
    using duration = std::chrono::duration<T>;

    uint64_t step_count_ = 0;
    TTimePoint wall_clock_start_;
};

}} //namespace
#endif
